﻿using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using Loki.Resources;

namespace Loki.Utils
{
    public class LokiErrorService : LokiService, IErrorService
    {
        public override Guid ID
        {
            get { return new Guid(_ID); }
        }

        public override string Name
        {
            get { return NAME; }
        }

        private const string NAME = "Loki error handler";
        private const string _ID = "";

        #region IErrorService Members

        private ConcurrentDictionary<Type, Func<Exception>> _ParameterLessTypeBuilders = new ConcurrentDictionary<Type, Func<Exception>>();
        private ConcurrentDictionary<Type, Func<string, Exception>> _StringTypeBuilders = new ConcurrentDictionary<Type, Func<string, Exception>>();
        private ConcurrentDictionary<Type, Func<string, Exception, Exception>> _FullTypeBuilders = new ConcurrentDictionary<Type, Func<string, Exception, Exception>>();

        private void BuildTypeConstructors<T>() where T : Exception
        {
            Type L_ExceptionType = typeof(T);
            if (!_ParameterLessTypeBuilders.ContainsKey(L_ExceptionType))
            {
                ConstructorInfo L_Info = L_ExceptionType.GetConstructor(Type.EmptyTypes);
                if (L_Info != null)
                {
                    Func<Exception> L_Method = Expression.Lambda<Func<T>>(Expression.New(L_Info)).Compile();
                    _ParameterLessTypeBuilders.TryAdd(L_ExceptionType, L_Method);
                }
                else
                {
                    throw BuildErrorFormat<ArgumentException>(ErrorMessages.Error_InvalidExceptionType, typeof(T));
                }
            }

            if (!_StringTypeBuilders.ContainsKey(L_ExceptionType))
            {
                ConstructorInfo L_Info = L_ExceptionType.GetConstructor(new Type[] { typeof(string) });
                if (L_Info != null)
                {
                    ParameterExpression L_MessageExpression = Expression.Parameter(typeof(string));
                    Func<string, Exception> L_Method = Expression.Lambda<Func<string, T>>(Expression.New(L_Info, L_MessageExpression), L_MessageExpression).Compile();
                    _StringTypeBuilders.TryAdd(L_ExceptionType, L_Method);
                }
                else
                {
                    throw BuildErrorFormat<ArgumentException>(ErrorMessages.Error_InvalidExceptionType, typeof(T));
                }
            }

            if (!_FullTypeBuilders.ContainsKey(L_ExceptionType))
            {
                ConstructorInfo L_Info = L_ExceptionType.GetConstructor(new Type[] { typeof(string), typeof(Exception) });
                if (L_Info != null)
                {
                    ParameterExpression L_MessageExpression = Expression.Parameter(typeof(string));
                    ParameterExpression L_ExceptionExpression = Expression.Parameter(typeof(Exception));
                    Func<string, Exception, Exception> L_Method = Expression.Lambda<Func<string, Exception, T>>(Expression.New(L_Info, L_MessageExpression, L_ExceptionExpression), L_MessageExpression, L_ExceptionExpression).Compile();
                    _FullTypeBuilders.TryAdd(L_ExceptionType, L_Method);
                }
                else
                {
                    throw BuildErrorFormat<ArgumentException>(ErrorMessages.Error_InvalidExceptionType, typeof(T));
                }
            }
        }

        public T BuildError<T>(string P_Message, ILog P_Log, Exception P_InnerException) where T : Exception
        {
            // check type cache
            BuildTypeConstructors<T>();

            // log exception
            if (P_Log != null)
            {
                P_Log.Error(P_Message, P_InnerException);
            }
            else
            {
                Log.Error(P_Message, P_InnerException);
            }

            // build and launch
            if (P_InnerException == null)
            {
                return _StringTypeBuilders[typeof(T)](P_Message) as T;
            }
            else
            {
                return _FullTypeBuilders[typeof(T)](P_Message, P_InnerException) as T;
            }
        }

        public T BuildErrorFormat<T>(string P_Message, ILog P_Log, Exception P_InnerException, params object[] P_Params) where T : Exception
        {
            // check type cache
            BuildTypeConstructors<T>();

            // log exception
            if (P_Log != null)
            {
                P_Log.ErrorFormat(P_Message, P_InnerException, P_Params);
            }
            else
            {
                Log.ErrorFormat(P_Message, P_InnerException, P_Params);
            }

            // build and launch
            if (P_InnerException == null)
            {
                return _StringTypeBuilders[typeof(T)](string.Format(CultureInfo.InvariantCulture, P_Message, P_Params)) as T;
            }
            else
            {
                return _FullTypeBuilders[typeof(T)](string.Format(CultureInfo.InvariantCulture, P_Message, P_Params), P_InnerException) as T;
            }
        }

        public T BuildError<T>(string P_Message, ILog P_Log) where T : Exception
        {
            return BuildError<T>(P_Message, P_Log, null);
        }

        public T BuildErrorFormat<T>(string P_Message, ILog P_Log, params object[] P_Params) where T : Exception
        {
            return BuildErrorFormat<T>(P_Message, P_Log, null, P_Params);
        }

        public void LogError(string P_Message, ILog P_Log, Exception P_InnerException, params object[] P_Params)
        {
            if (P_Log != null)
            {
                P_Log.ErrorFormat(P_Message, P_InnerException);
            }
            else
            {
                Log.ErrorFormat(P_Message, P_InnerException);
            }
        }

        #endregion
    }
}
